import java.awt.Color;
import java.awt.Point;
import java.awt.geom.Point2D;
import java.util.HashMap;

import ownclasses.Cluster;
import ownclasses.Pixel;

import ij.IJ;
import ij.ImagePlus;
import ij.gui.PointRoi;
import ij.plugin.filter.GaussianBlur;
import ij.plugin.filter.PlugInFilter;
import ij.process.Blitter;
import ij.process.FloatProcessor;
import ij.process.ImageProcessor;


public class A05_Texture_Maps implements PlugInFilter{

	static double sigma = 10;	// width of Gaussian blur
	static double sigma2 = 2;
	
	public float[] HL = {1,4,6,4,1};
	public float[] HE = {-1,-2,0,2,1};
	public float[] HS = {-1,0,2,0,-1};
	public float[] HR = {1,-4,6,-4,1};
	
	public Color[] col = {  Color.blue,
							Color.cyan,
							Color.DARK_GRAY,
							Color.green,
							Color.LIGHT_GRAY,
							Color.magenta,
							Color.orange,
							Color.pink,
							Color.red,
							Color.yellow,
							Color.white,
							Color.black,
							new Color(143/255,0,255/255),
							new Color(0,100/255,0),
							new Color(0,100/255,255/255),
							new Color(255/255,80/255,170/255)
	};
	
	@Override
	public int setup(String arg0, ImagePlus arg1) {
		return DOES_8G + DOES_RGB + NO_CHANGES;	// does 8-bit and RGB images
	}
	
	@Override
	public void run(ImageProcessor ip) {
		FloatProcessor fp = (FloatProcessor) ip.convertToFloat();
		
		//Make Lokal Average
		// create 2 FloatProcessors (duplicates of ip):
		FloatProcessor fpOrig = (FloatProcessor) ip.convertToFloat();
		FloatProcessor fpBlur = (FloatProcessor) fpOrig.duplicate();
		ImageProcessor cp = ip.convertToRGB();
		

		// apply a Gaussian blur with specified sigma:
		GaussianBlur gb = new GaussianBlur();
		gb.blurGaussian(fpBlur, sigma, sigma, 0.02);

		// subtract the blurred image from the original:
		fpOrig.copyBits(fpBlur, 0, 0, Blitter.SUBTRACT);

		// recalculate the limiting values (for display only):
		fpOrig.resetMinAndMax();
		fpBlur.resetMinAndMax();
		(new ImagePlus("Preprocessed", fpOrig)).show();
		
		//Filter anlegen
		HashMap<String, FloatProcessor> energyMaps = new HashMap<String, FloatProcessor>();
		energyMaps = CreateEnergyMaps(fpOrig, energyMaps);
		HashMap<String, FloatProcessor> finalMaps = new HashMap<String, FloatProcessor>();
		finalMaps.put("EEE", energyMaps.get("HEE"));
		finalMaps.put("ESS", energyMaps.get("HSS"));
		finalMaps.put("ERR", energyMaps.get("HRR"));
		finalMaps.put("EEL", addAndHalf(energyMaps.get("HEL"), energyMaps.get("HLE")));
		finalMaps.put("ESL", addAndHalf(energyMaps.get("HSL"), energyMaps.get("HLS")));
		finalMaps.put("ERL", addAndHalf(energyMaps.get("HRL"), energyMaps.get("HLR")));
		finalMaps.put("ESE", addAndHalf(energyMaps.get("HSE"), energyMaps.get("HES")));
		finalMaps.put("ERE", addAndHalf(energyMaps.get("HRE"), energyMaps.get("HER")));
		finalMaps.put("ERS", addAndHalf(energyMaps.get("HRS"), energyMaps.get("HSR")));
		
		//EnergyMaps ausgeben
		for(String k : finalMaps.keySet()) {
			ImageProcessor ipx = finalMaps.get(k);
			(new ImagePlus(k, ipx)).show();
		}
		
		//Multidimensional Image
		Pixel[][] source = new Pixel[fpOrig.getWidth()][fpOrig.getHeight()];
		for(int i = 0; i < source.length; i++)
		{
			for(int j = 0; j < source[0].length; j++)
			{
				float[] a = new float[9];
				a[0] = finalMaps.get("EEE").getPixelValue(i, j);
				a[1] = finalMaps.get("ESS").getPixelValue(i, j);
				a[2] = finalMaps.get("ERR").getPixelValue(i, j);
				a[3] = finalMaps.get("EEL").getPixelValue(i, j);
				a[4] = finalMaps.get("ESL").getPixelValue(i, j);
				a[5] = finalMaps.get("ERL").getPixelValue(i, j);
				a[6] = finalMaps.get("ESE").getPixelValue(i, j);
				a[7] = finalMaps.get("ERE").getPixelValue(i, j);
				a[8] = finalMaps.get("ERS").getPixelValue(i, j);
				source[i][j] = new Pixel(new Point(i, j), a);
			}
		}
		
		//K-means feature clustering
		
		int clusterNum = 16;
		
		Cluster[] cluster = new Cluster[clusterNum];
		
		
		//pick 16 Cluster center
		for(int i = 0; i < 16; i++)
		{
			int randX = (int)(Math.random()*fpOrig.getWidth());
			///4.0 + i%4*fpOrig.getWidth()/4.0);
			int randY = (int)(Math.random()*fpOrig.getHeight());
			///4.0 + i/4*fpOrig.getHeight()/4.0);
//			IJ.log("Mittelpunkt"+i+" | X: "+randX+" | Y: "+randY);
			cluster[i] = new Cluster(new Point(randX, randY), source[randX][randY].getValue());
		}
		int changedClusters = 1;
		int x = 10;
		while(changedClusters > 0 && x > 0)
		{
			changedClusters = 0;
			for(int j = 0; j < source.length; j++)
			{
				for(int k = 0; k < source[0].length; k++)
				{
					double distance = Double.MAX_VALUE;
					int clusterNumb = 20;
					for(int l = 0; l < cluster.length; l++)
					{
						if(distance > cluster[l].calcDist(source[j][k].getValue()))
						{
							distance = cluster[l].calcDist(source[j][k].getValue());
							clusterNumb = l;
						}
					}
					if(source[j][k].getCluster() == null || source[j][k].getCluster() != cluster[clusterNumb])
					{
						//IJ.log("Cluster setzen");
						source[j][k].setCluster(cluster[clusterNumb]);
						cluster[clusterNumb].add(source[j][k]);
						changedClusters++;
					}
				}
			}
			IJ.log("Changed Clusters: "+changedClusters);
			for(int i = 0; i < cluster.length; i++)
			{
				cluster[i].recomputeCenter();
				IJ.log("Cluster "+i+" Punkte: "+cluster[i].getPixels());
			}
			x--;
		}
		if(x <= 0)
		{
			IJ.log("über 20 durchgänge");
		}
		
		for(int i = 0; i < cluster.length; i++)
		{
			IJ.log("Cluster "+i+" Punkte: "+cluster[i].getPixels());
		}
		
		//Make ColorPicture
		for(int i = 0; i < cp.getWidth(); i++)
		{
			for(int j = 0; j < cp.getHeight(); j++)
			{
				for(int k = 0; k < cluster.length; k++)
				{
					if(source[i][j].getCluster() == cluster[k])
					{
						cp.setColor(col[k]);
						cp.drawPixel(i, j);
						break;
					}
				}
			}
		}
		(new ImagePlus("K-means", cp)).show();
	}

	private FloatProcessor addAndHalf(FloatProcessor fp1, FloatProcessor fp2) {
		FloatProcessor ret = (FloatProcessor) fp1.duplicate();
		for(int i = 0; i < ret.getWidth(); i++) {
			for(int j = 0; j < ret.getHeight(); j++) {
				ret.setf(i, j,  0.5f * (fp1.getf(i,j)+fp2.getf(i,j)));
			}
		}
		return ret;
	}

	private HashMap<String, FloatProcessor> CreateEnergyMaps(FloatProcessor fpOrig, HashMap<String, FloatProcessor> energyMaps) {
		GaussianBlur gb = new GaussianBlur();
		
		FloatProcessor I_LL = (FloatProcessor) fpOrig.duplicate();
		I_LL.convolve(HL, 1, 5);
		I_LL.convolve(HL, 5, 1);
		I_LL.abs();
		gb.blurGaussian(I_LL, sigma2, sigma2, 0.02);
		energyMaps.put("HLL", I_LL);
		I_LL = (FloatProcessor) fpOrig.duplicate();
		I_LL.convolve(HL, 1, 5);
		I_LL.convolve(HE, 5, 1);
		I_LL.abs();
		gb.blurGaussian(I_LL, sigma2, sigma2, 0.02);
		energyMaps.put("HLE", I_LL);
		I_LL = (FloatProcessor) fpOrig.duplicate();
		I_LL.convolve(HL, 1, 5);
		I_LL.convolve(HS, 5, 1);
		I_LL.abs();
		gb.blurGaussian(I_LL, sigma2, sigma2, 0.02);
		energyMaps.put("HLS", I_LL);
		I_LL = (FloatProcessor) fpOrig.duplicate();
		I_LL.convolve(HL, 1, 5);
		I_LL.convolve(HR, 5, 1);
		I_LL.abs();
		gb.blurGaussian(I_LL, sigma2, sigma2, 0.02);
		energyMaps.put("HLR", I_LL);
		
		I_LL = (FloatProcessor) fpOrig.duplicate();
		I_LL.convolve(HE, 1, 5);
		I_LL.convolve(HL, 5, 1);
		I_LL.abs();
		gb.blurGaussian(I_LL, sigma2, sigma2, 0.02);
		energyMaps.put("HEL", I_LL);
		I_LL = (FloatProcessor) fpOrig.duplicate();
		I_LL.convolve(HE, 1, 5);
		I_LL.convolve(HE, 5, 1);
		I_LL.abs();
		gb.blurGaussian(I_LL, sigma2, sigma2, 0.02);
		energyMaps.put("HEE", I_LL);
		I_LL = (FloatProcessor) fpOrig.duplicate();
		I_LL.convolve(HE, 1, 5);
		I_LL.convolve(HS, 5, 1);
		I_LL.abs();
		gb.blurGaussian(I_LL, sigma2, sigma2, 0.02);
		energyMaps.put("HES", I_LL);
		I_LL = (FloatProcessor) fpOrig.duplicate();
		I_LL.convolve(HE, 1, 5);
		I_LL.convolve(HR, 5, 1);
		I_LL.abs();
		gb.blurGaussian(I_LL, sigma2, sigma2, 0.02);
		energyMaps.put("HER", I_LL);

		I_LL = (FloatProcessor) fpOrig.duplicate();
		I_LL.convolve(HS, 1, 5);
		I_LL.convolve(HL, 5, 1);
		I_LL.abs();
		gb.blurGaussian(I_LL, sigma2, sigma2, 0.02);
		energyMaps.put("HSL", I_LL);
		I_LL = (FloatProcessor) fpOrig.duplicate();
		I_LL.convolve(HS, 1, 5);
		I_LL.convolve(HE, 5, 1);
		I_LL.abs();
		gb.blurGaussian(I_LL, sigma2, sigma2, 0.02);
		energyMaps.put("HSE", I_LL);
		I_LL = (FloatProcessor) fpOrig.duplicate();
		I_LL.convolve(HS, 1, 5);
		I_LL.convolve(HS, 5, 1);
		I_LL.abs();
		gb.blurGaussian(I_LL, sigma2, sigma2, 0.02);
		energyMaps.put("HSS", I_LL);
		I_LL = (FloatProcessor) fpOrig.duplicate();
		I_LL.convolve(HS, 1, 5);
		I_LL.convolve(HR, 5, 1);
		I_LL.abs();
		gb.blurGaussian(I_LL, sigma2, sigma2, 0.02);
		energyMaps.put("HSR", I_LL);

		I_LL = (FloatProcessor) fpOrig.duplicate();
		I_LL.convolve(HR, 1, 5);
		I_LL.convolve(HL, 5, 1);
		I_LL.abs();
		gb.blurGaussian(I_LL, sigma2, sigma2, 0.02);
		energyMaps.put("HRL", I_LL);
		I_LL = (FloatProcessor) fpOrig.duplicate();
		I_LL.convolve(HR, 1, 5);
		I_LL.convolve(HE, 5, 1);
		I_LL.abs();
		gb.blurGaussian(I_LL, sigma2, sigma2, 0.02);
		energyMaps.put("HRE", I_LL);
		I_LL = (FloatProcessor) fpOrig.duplicate();
		I_LL.convolve(HR, 1, 5);
		I_LL.convolve(HS, 5, 1);
		I_LL.abs();
		gb.blurGaussian(I_LL, sigma2, sigma2, 0.02);
		energyMaps.put("HRS", I_LL);
		I_LL = (FloatProcessor) fpOrig.duplicate();
		I_LL.convolve(HR, 1, 5);
		I_LL.convolve(HR, 5, 1);
		I_LL.abs();
		gb.blurGaussian(I_LL, sigma2, sigma2, 0.02);
		energyMaps.put("HRR", I_LL);
		
		return energyMaps;
	}

	

}
